﻿// Inner Fire 游戏引擎库
// 场景分装 - 房间
//
// Copyright (c) 2023 by 尔西大帝. All rights reserved.
//
// 这个类分装各种场景的构建（都在一个文件里面太大了）
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2024-11-28

#include <dx_graphics.h>

#define mMainPassCB main_pass_

namespace ifire {
void DxGraphics::Pick_Update() {
  UpdateObjectCBs_V2();
  UpdateMaterialBuffer_V2();
  UpdateMainPassCB_V2();
}

void DxGraphics::Pick_InitPipeline() {
  mCamera.LookAt(XMFLOAT3(5.0f, 4.0f, -15.0f), XMFLOAT3(0.0f, 1.0f, 0.0f),
      XMFLOAT3(0.0f, 1.0f, 0.0f));

  Pick_LoadTextures();
  Pick_BuildRootSignature();
  Pick_BuildDescriptorHeaps();
  Pick_BuildShadersAndInputLayout();
  Pick_BuildCarGeometry();
  Pick_BuildMaterials();
  Pick_BuildRenderItems();
  Pick_BuildFrameResources();
  Pick_BuildPSOs();
}

void DxGraphics::Pick_Draw() {
  auto cmdListAlloc = mCurrFrameResource->CmdListAlloc;

  // Reuse the memory associated with command recording.
  // We can only reset when the associated command lists have finished execution
  // on the GPU.
  ThrowIfFailed(cmdListAlloc->Reset());

  // A command list can be reset after it has been added to the command queue
  // via ExecuteCommandList. Reusing the command list reuses memory.
  ThrowIfFailed(mCommandList->Reset(cmdListAlloc.Get(), mPSOs["opaque"].Get()));

  mCommandList->RSSetViewports(1, &mScreenViewport);
  mCommandList->RSSetScissorRects(1, &mScissorRect);

  // Indicate a state transition on the resource usage.
  auto to_render = CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
      D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
  mCommandList->ResourceBarrier(1, &to_render);

  // Clear the back buffer and depth buffer.
  auto back_buffer_view = CurrentBackBufferView();
  auto dsv = DepthStencilView();
  mCommandList->ClearRenderTargetView(
      back_buffer_view, Colors::LightSteelBlue, 0, nullptr);
  mCommandList->ClearDepthStencilView(dsv,
      D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);

  // Specify the buffers we are going to render to.
  mCommandList->OMSetRenderTargets(1, &back_buffer_view, true, &dsv);

  ID3D12DescriptorHeap* descriptorHeaps[] = {mSrvDescriptorHeap.Get()};
  mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);

  mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

  auto passCB = mCurrFrameResource->PassCB->Resource();
  mCommandList->SetGraphicsRootConstantBufferView(
      1, passCB->GetGPUVirtualAddress());

  // Bind all the materials used in this scene.  For structured buffers, we can
  // bypass the heap and set as a root descriptor.
  auto matBuffer = mCurrFrameResource->MaterialBuffer->Resource();
  mCommandList->SetGraphicsRootShaderResourceView(
      2, matBuffer->GetGPUVirtualAddress());

  // Bind all the textures used in this scene.  Observe
  // that we only have to specify the first descriptor in the table.
  // The root signature knows how many descriptors are expected in the table.
  mCommandList->SetGraphicsRootDescriptorTable(
      3, mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());

  Pick_DrawRenderItems(
      mCommandList.Get(), mRitemLayer[(int)RenderLayer::Opaque]);

  mCommandList->SetPipelineState(mPSOs["highlight"].Get());
  Pick_DrawRenderItems(
      mCommandList.Get(), mRitemLayer[(int)RenderLayer::Highlight]);

  // Indicate a state transition on the resource usage.
  auto to_present = CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
      D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
  mCommandList->ResourceBarrier(1, &to_present);

  // Done recording commands.
  ThrowIfFailed(mCommandList->Close());

  // Add the command list to the queue for execution.
  ID3D12CommandList* cmdsLists[] = {mCommandList.Get()};
  mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

  // Swap the back and front buffers
  ThrowIfFailed(mSwapChain->Present(0, 0));
  mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;

  // Advance the fence value to mark commands up to this fence point.
  mCurrFrameResource->Fence = ++mCurrentFence;

  // Add an instruction to the command queue to set a new fence point.
  // Because we are on the GPU timeline, the new fence point won't be
  // set until the GPU finishes processing all the commands prior to this
  // Signal().
  mCommandQueue->Signal(mFence.Get(), mCurrentFence);
}

void DxGraphics::Pick_DrawRenderItems(ID3D12GraphicsCommandList* cmdList,
    const std::vector<RenderItem*>& ritems) {
  // 这里用的V2
  UINT objCBByteSize =
      dxutils::calc_constant_buffer_size(sizeof(ObjectConstantsV2));

  auto objectCB = mCurrFrameResource->ObjectCB_V2->Resource();

  // For each render item...
  for (size_t i = 0; i < ritems.size(); ++i) {
    auto ri = ritems[i];

    if (ri->Visible == false)
      continue;

    auto vertex_view = ri->Geo->VertexBufferView();
    auto index_view = ri->Geo->IndexBufferView();
    cmdList->IASetVertexBuffers(0, 1, &vertex_view);
    cmdList->IASetIndexBuffer(&index_view);
    cmdList->IASetPrimitiveTopology(ri->PrimitiveType);

    D3D12_GPU_VIRTUAL_ADDRESS objCBAddress =
        objectCB->GetGPUVirtualAddress() + ri->ObjCBIndex * objCBByteSize;

    cmdList->SetGraphicsRootConstantBufferView(0, objCBAddress);

    cmdList->DrawIndexedInstanced(
        ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);
  }
}

void DxGraphics::Pick_LoadTextures() {
  auto defaultDiffuseTex = std::make_unique<Texture>();
  defaultDiffuseTex->Name = "defaultDiffuseTex";
  defaultDiffuseTex->Filename = "Textures/white1x1.dds";
  ThrowIfFailed(LoadDDS(defaultDiffuseTex->Filename.c_str(),
      defaultDiffuseTex->Resource, defaultDiffuseTex->UploadHeap));

  mTextures[defaultDiffuseTex->Name] = std::move(defaultDiffuseTex);
}

void DxGraphics::Pick_BuildRootSignature() {
  CD3DX12_DESCRIPTOR_RANGE texTable;
  texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 4, 0, 0);

  // Root parameter can be a table, root descriptor or root constants.
  CD3DX12_ROOT_PARAMETER slotRootParameter[4];

  // Perfomance TIP: Order from most frequent to least frequent.
  slotRootParameter[0].InitAsConstantBufferView(0);
  slotRootParameter[1].InitAsConstantBufferView(1);
  slotRootParameter[2].InitAsShaderResourceView(0, 1);
  slotRootParameter[3].InitAsDescriptorTable(
      1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL);

  auto staticSamplers = GetStaticSamplers();

  // A root signature is an array of root parameters.
  CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(4, slotRootParameter,
      (UINT)staticSamplers.size(), staticSamplers.data(),
      D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

  // create a root signature with a single slot which points to a descriptor
  // range consisting of a single constant buffer
  ComPtr<ID3DBlob> serializedRootSig = nullptr;
  ComPtr<ID3DBlob> errorBlob = nullptr;
  HRESULT hr =
      D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
          serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());

  if (errorBlob != nullptr) {
    ::OutputDebugStringA((char*)errorBlob->GetBufferPointer());
  }
  ThrowIfFailed(hr);

  ThrowIfFailed(md3dDevice->CreateRootSignature(0,
      serializedRootSig->GetBufferPointer(), serializedRootSig->GetBufferSize(),
      IID_PPV_ARGS(mRootSignature.GetAddressOf())));
}

void DxGraphics::Pick_BuildDescriptorHeaps() {
  //
  // Create the SRV heap.
  //
  D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
  srvHeapDesc.NumDescriptors = 1;
  srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
  srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
  ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
      &srvHeapDesc, IID_PPV_ARGS(&mSrvDescriptorHeap)));

  //
  // Fill out the heap with actual descriptors.
  //
  CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(
      mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

  auto defaultDiffuseTex = mTextures["defaultDiffuseTex"]->Resource;

  D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
  srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
  srvDesc.Format = defaultDiffuseTex->GetDesc().Format;
  srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
  srvDesc.Texture2D.MostDetailedMip = 0;
  srvDesc.Texture2D.MipLevels = defaultDiffuseTex->GetDesc().MipLevels;
  srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
  md3dDevice->CreateShaderResourceView(
      defaultDiffuseTex.Get(), &srvDesc, hDescriptor);
}

void DxGraphics::Pick_BuildShadersAndInputLayout() {
  const D3D_SHADER_MACRO alphaTestDefines[] = {"ALPHA_TEST", "1", NULL, NULL};

  mShaders["standardVS"] = d3dUtil::CompileShader(
      "Shaders\\Default_Pick.hlsl", nullptr, "VS", "vs_5_1");
  mShaders["opaquePS"] = d3dUtil::CompileShader(
      "Shaders\\Default_Pick.hlsl", nullptr, "PS", "ps_5_1");

  mInputLayout = {
      {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
          D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
      {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,
          D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
      {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24,
          D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
  };
}

void DxGraphics::Pick_BuildCarGeometry() {
  std::ifstream fin("Models/car.txt");

  if (!fin) {
    MessageBox(0, "Models/car.txt not found.", 0, 0);
    return;
  }

  UINT vcount = 0;
  UINT tcount = 0;
  std::string ignore;

  fin >> ignore >> vcount;
  fin >> ignore >> tcount;
  fin >> ignore >> ignore >> ignore >> ignore;

  XMFLOAT3 vMinf3(
      +MathHelper::Infinity, +MathHelper::Infinity, +MathHelper::Infinity);
  XMFLOAT3 vMaxf3(
      -MathHelper::Infinity, -MathHelper::Infinity, -MathHelper::Infinity);

  XMVECTOR vMin = XMLoadFloat3(&vMinf3);
  XMVECTOR vMax = XMLoadFloat3(&vMaxf3);

  std::vector<Vertex> vertices(vcount);
  for (UINT i = 0; i < vcount; ++i) {
    fin >> vertices[i].Pos.x >> vertices[i].Pos.y >> vertices[i].Pos.z;
    fin >> vertices[i].Normal.x >> vertices[i].Normal.y >> vertices[i].Normal.z;

    XMVECTOR P = XMLoadFloat3(&vertices[i].Pos);

    vertices[i].TexC = {0.0f, 0.0f};

    vMin = XMVectorMin(vMin, P);
    vMax = XMVectorMax(vMax, P);
  }

  BoundingBox bounds;
  XMStoreFloat3(&bounds.Center, 0.5f * (vMin + vMax));
  XMStoreFloat3(&bounds.Extents, 0.5f * (vMax - vMin));

  fin >> ignore;
  fin >> ignore;
  fin >> ignore;

  std::vector<std::int32_t> indices(3 * tcount);
  for (UINT i = 0; i < tcount; ++i) {
    fin >> indices[i * 3 + 0] >> indices[i * 3 + 1] >> indices[i * 3 + 2];
  }

  fin.close();

  //
  // Pack the indices of all the meshes into one index buffer.
  //

  const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);

  const UINT ibByteSize = (UINT)indices.size() * sizeof(std::int32_t);

  auto geo = std::make_unique<MeshGeometry>();
  geo->Name = "carGeo";

  ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU));
  CopyMemory(
      geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);

  ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
  CopyMemory(
      geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

  geo->VertexBufferGPU = CreateDefaultBuffer(
      vertices.data(), vbByteSize, geo->VertexBufferUploader);

  geo->IndexBufferGPU =
      CreateDefaultBuffer(indices.data(), ibByteSize, geo->IndexBufferUploader);

  geo->VertexByteStride = sizeof(Vertex);
  geo->VertexBufferByteSize = vbByteSize;
  geo->IndexFormat = DXGI_FORMAT_R32_UINT;
  geo->IndexBufferByteSize = ibByteSize;

  SubmeshGeometry submesh;
  submesh.IndexCount = (UINT)indices.size();
  submesh.StartIndexLocation = 0;
  submesh.BaseVertexLocation = 0;
  submesh.Bounds = bounds;

  geo->DrawArgs["car"] = submesh;

  mGeometries[geo->Name] = std::move(geo);
}

void DxGraphics::Pick_BuildMaterials() {
  auto gray0 = std::make_unique<Material>();
  gray0->Name = "gray0";
  gray0->MatCBIndex = 0;
  gray0->DiffuseSrvHeapIndex = 0;
  gray0->DiffuseAlbedo = XMFLOAT4(0.7f, 0.7f, 0.7f, 1.0f);
  gray0->FresnelR0 = XMFLOAT3(0.04f, 0.04f, 0.04f);
  gray0->Roughness = 0.0f;

  auto highlight0 = std::make_unique<Material>();
  highlight0->Name = "highlight0";
  highlight0->MatCBIndex = 1;
  highlight0->DiffuseSrvHeapIndex = 0;
  highlight0->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 0.0f, 0.6f);
  highlight0->FresnelR0 = XMFLOAT3(0.06f, 0.06f, 0.06f);
  highlight0->Roughness = 0.0f;

  mMaterials["gray0"] = std::move(gray0);
  mMaterials["highlight0"] = std::move(highlight0);
}

void DxGraphics::Pick_BuildRenderItems() {
  auto carRitem = std::make_unique<RenderItem>();
  XMStoreFloat4x4(&carRitem->World, XMMatrixScaling(1.0f, 1.0f, 1.0f) *
                                        XMMatrixTranslation(0.0f, 1.0f, 0.0f));
  XMStoreFloat4x4(&carRitem->TexTransform, XMMatrixScaling(1.0f, 1.0f, 1.0f));
  carRitem->ObjCBIndex = 0;
  carRitem->Mat = mMaterials["gray0"].get();
  carRitem->Geo = mGeometries["carGeo"].get();
  carRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
  carRitem->Bounds = carRitem->Geo->DrawArgs["car"].Bounds;
  carRitem->IndexCount = carRitem->Geo->DrawArgs["car"].IndexCount;
  carRitem->StartIndexLocation =
      carRitem->Geo->DrawArgs["car"].StartIndexLocation;
  carRitem->BaseVertexLocation =
      carRitem->Geo->DrawArgs["car"].BaseVertexLocation;
  mRitemLayer[(int)RenderLayer::Opaque].push_back(carRitem.get());

  auto pickedRitem = std::make_unique<RenderItem>();
  pickedRitem->World = MathHelper::Identity4x4();
  pickedRitem->TexTransform = MathHelper::Identity4x4();
  pickedRitem->ObjCBIndex = 1;
  pickedRitem->Mat = mMaterials["highlight0"].get();
  pickedRitem->Geo = mGeometries["carGeo"].get();
  pickedRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;

  // Picked triangle is not visible until one is picked.
  pickedRitem->Visible = false;

  // DrawCall parameters are filled out when a triangle is picked.
  pickedRitem->IndexCount = 0;
  pickedRitem->StartIndexLocation = 0;
  pickedRitem->BaseVertexLocation = 0;
  mPickedRitem = pickedRitem.get();
  mRitemLayer[(int)RenderLayer::Highlight].push_back(pickedRitem.get());

  mAllRitems.push_back(std::move(carRitem));
  mAllRitems.push_back(std::move(pickedRitem));
}

void DxGraphics::Pick_BuildFrameResources() {
  FrameResourceDesc desc = {};
  desc.PassCount = 1;
  desc.ObjectCount = static_cast<UINT>(items_.size());
  desc.MaterialCount = static_cast<UINT>(materials_.size());

  for (int i = 0; i < FRAME_RESOURCE_COUNT; i++) {
    frame_resources_.push_back(
        std::make_unique<FrameResource>(device_.Get(), &desc));
  }
}

void DxGraphics::Pick_BuildPSOs() {
  D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePsoDesc;

  //
  // PSO for opaque objects.
  //
  ZeroMemory(&opaquePsoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
  opaquePsoDesc.InputLayout = {mInputLayout.data(), (UINT)mInputLayout.size()};
  opaquePsoDesc.pRootSignature = mRootSignature.Get();
  opaquePsoDesc.VS = {
      reinterpret_cast<BYTE*>(mShaders["standardVS"]->GetBufferPointer()),
      mShaders["standardVS"]->GetBufferSize()};
  opaquePsoDesc.PS = {
      reinterpret_cast<BYTE*>(mShaders["opaquePS"]->GetBufferPointer()),
      mShaders["opaquePS"]->GetBufferSize()};
  opaquePsoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
  opaquePsoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
  opaquePsoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
  opaquePsoDesc.SampleMask = UINT_MAX;
  opaquePsoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
  opaquePsoDesc.NumRenderTargets = 1;
  opaquePsoDesc.RTVFormats[0] = mBackBufferFormat;
  opaquePsoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
  opaquePsoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
  opaquePsoDesc.DSVFormat = mDepthStencilFormat;
  ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(
      &opaquePsoDesc, IID_PPV_ARGS(&mPSOs["opaque"])));

  //
  // PSO for highlight objects
  //

  D3D12_GRAPHICS_PIPELINE_STATE_DESC highlightPsoDesc = opaquePsoDesc;

  // Change the depth test from < to <= so that if we draw the same triangle
  // twice, it will still pass the depth test.  This is needed because we redraw
  // the picked triangle with a different material to highlight it.  If we do
  // not use <=, the triangle will fail the depth test the 2nd time we try and
  // draw it.
  highlightPsoDesc.DepthStencilState.DepthFunc =
      D3D12_COMPARISON_FUNC_LESS_EQUAL;

  // Standard transparency blending.
  D3D12_RENDER_TARGET_BLEND_DESC transparencyBlendDesc;
  transparencyBlendDesc.BlendEnable = true;
  transparencyBlendDesc.LogicOpEnable = false;
  transparencyBlendDesc.SrcBlend = D3D12_BLEND_SRC_ALPHA;
  transparencyBlendDesc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
  transparencyBlendDesc.BlendOp = D3D12_BLEND_OP_ADD;
  transparencyBlendDesc.SrcBlendAlpha = D3D12_BLEND_ONE;
  transparencyBlendDesc.DestBlendAlpha = D3D12_BLEND_ZERO;
  transparencyBlendDesc.BlendOpAlpha = D3D12_BLEND_OP_ADD;
  transparencyBlendDesc.LogicOp = D3D12_LOGIC_OP_NOOP;
  transparencyBlendDesc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;

  highlightPsoDesc.BlendState.RenderTarget[0] = transparencyBlendDesc;
  ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(
      &highlightPsoDesc, IID_PPV_ARGS(&mPSOs["highlight"])));
}

void DxGraphics::UpdateObjectCBs_V2() {
  auto currObjectCB = mCurrFrameResource->ObjectCB_V2.get();
  for (auto& e : mAllRitems) {
    // Only update the cbuffer data if the constants have changed.
    // This needs to be tracked per frame resource.
    if (e->NumFramesDirty > 0) {
      XMMATRIX world = XMLoadFloat4x4(&e->World);
      XMMATRIX texTransform = XMLoadFloat4x4(&e->TexTransform);

      ObjectConstantsV2 objConstants;
      XMStoreFloat4x4(&objConstants.World, XMMatrixTranspose(world));
      XMStoreFloat4x4(
          &objConstants.TexTransform, XMMatrixTranspose(texTransform));
      objConstants.MaterialIndex = e->Mat->MatCBIndex;

      currObjectCB->CopyData(e->ObjCBIndex, objConstants);

      // Next FrameResource need to be updated too.
      e->NumFramesDirty--;
    }
  }
}

void DxGraphics::UpdateMaterialBuffer_V2() {
  auto currMaterialBuffer = mCurrFrameResource->MaterialBuffer.get();
  for (auto& e : mMaterials) {
    // Only update the cbuffer data if the constants have changed.  If the
    // cbuffer data changes, it needs to be updated for each FrameResource.
    Material* mat = e.second.get();
    if (mat->NumFramesDirty > 0) {
      XMMATRIX matTransform = XMLoadFloat4x4(&mat->MatTransform);

      MaterialData matData;
      matData.DiffuseAlbedo = mat->DiffuseAlbedo;
      matData.FresnelR0 = mat->FresnelR0;
      matData.Roughness = mat->Roughness;
      XMStoreFloat4x4(&matData.MatTransform, XMMatrixTranspose(matTransform));
      matData.DiffuseMapIndex = mat->DiffuseSrvHeapIndex;
      matData.NormalMapIndex = mat->NormalSrvHeapIndex;

      currMaterialBuffer->CopyData(mat->MatCBIndex, matData);

      // Next FrameResource need to be updated too.
      mat->NumFramesDirty--;
    }
  }
}

void DxGraphics::UpdateMainPassCB_V2() {
  XMMATRIX view = mCamera.GetView();
  XMMATRIX proj = mCamera.GetProj();

  XMMATRIX viewProj = XMMatrixMultiply(view, proj);
  auto det = XMMatrixDeterminant(view);
  XMMATRIX invView = XMMatrixInverse(&det, view);
  det = XMMatrixDeterminant(proj);
  XMMATRIX invProj = XMMatrixInverse(&det, proj);
  det = XMMatrixDeterminant(viewProj);
  XMMATRIX invViewProj = XMMatrixInverse(&det, viewProj);
  XMMATRIX shadowTransform = XMLoadFloat4x4(&mShadowTransform);
  XMStoreFloat4x4(&mMainPassCB.View, XMMatrixTranspose(view));
  XMStoreFloat4x4(&mMainPassCB.InvView, XMMatrixTranspose(invView));
  XMStoreFloat4x4(&mMainPassCB.Proj, XMMatrixTranspose(proj));
  XMStoreFloat4x4(&mMainPassCB.InvProj, XMMatrixTranspose(invProj));
  XMStoreFloat4x4(&mMainPassCB.ViewProj, XMMatrixTranspose(viewProj));
  XMStoreFloat4x4(&mMainPassCB.InvViewProj, XMMatrixTranspose(invViewProj));
  XMStoreFloat4x4(
      &mMainPassCB.ShadowTransform, XMMatrixTranspose(shadowTransform));
  mMainPassCB.EyePosW = mCamera.GetPosition3f();
  mMainPassCB.RenderTargetSize =
      XMFLOAT2((float)mClientWidth, (float)mClientHeight);
  mMainPassCB.InvRenderTargetSize =
      XMFLOAT2(1.0f / mClientWidth, 1.0f / mClientHeight);
  mMainPassCB.NearZ = 1.0f;
  mMainPassCB.FarZ = 1000.0f;
  mMainPassCB.TotalTime = g_timer.TotalTime();
  mMainPassCB.DeltaTime = g_timer.DeltaTime();
  mMainPassCB.AmbientLight = {0.25f, 0.25f, 0.35f, 1.0f};
  mMainPassCB.Lights[0].Direction = {0.57735f, -0.57735f, 0.57735f};
  mMainPassCB.Lights[0].Strength = {0.8f, 0.8f, 0.8f};
  mMainPassCB.Lights[1].Direction = {-0.57735f, -0.57735f, 0.57735f};
  mMainPassCB.Lights[1].Strength = {0.4f, 0.4f, 0.4f};
  mMainPassCB.Lights[2].Direction = {0.0f, -0.707f, -0.707f};
  mMainPassCB.Lights[2].Strength = {0.2f, 0.2f, 0.2f};

  auto currPassCB = mCurrFrameResource->PassCB.get();
  currPassCB->CopyData(0, mMainPassCB);
}

void DxGraphics::Pick(int sx, int sy) {
  XMFLOAT4X4 P = mCamera.GetProj4x4f();

  // Compute picking ray in view space.
  float vx = (+2.0f * sx / mClientWidth - 1.0f) / P(0, 0);
  float vy = (-2.0f * sy / mClientHeight + 1.0f) / P(1, 1);

  // Ray definition in view space.
  XMVECTOR rayOrigin = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
  XMVECTOR rayDir = XMVectorSet(vx, vy, 1.0f, 0.0f);

  XMMATRIX V = mCamera.GetView();
  auto det = XMMatrixDeterminant(V);
  XMMATRIX invView = XMMatrixInverse(&det, V);

  // Assume nothing is picked to start, so the picked render-item is invisible.
  mPickedRitem->Visible = false;

  // Check if we picked an opaque render item.  A real app might keep a separate
  // "picking list" of objects that can be selected.
  for (auto ri : mRitemLayer[(int)RenderLayer::Opaque]) {
    auto geo = ri->Geo;

    // Skip invisible render-items.
    if (ri->Visible == false)
      continue;

    XMMATRIX W = XMLoadFloat4x4(&ri->World);
    det = XMMatrixDeterminant(W);
    XMMATRIX invWorld = XMMatrixInverse(&det, W);

    // Tranform ray to vi space of Mesh.
    XMMATRIX toLocal = XMMatrixMultiply(invView, invWorld);

    rayOrigin = XMVector3TransformCoord(rayOrigin, toLocal);
    rayDir = XMVector3TransformNormal(rayDir, toLocal);

    // Make the ray direction unit length for the intersection tests.
    rayDir = XMVector3Normalize(rayDir);

    // If we hit the bounding box of the Mesh, then we might have picked a Mesh
    // triangle, so do the ray/triangle tests.
    //
    // If we did not hit the bounding box, then it is impossible that we hit
    // the Mesh, so do not waste effort doing ray/triangle tests.
    float tmin = 0.0f;
    if (ri->Bounds.Intersects(rayOrigin, rayDir, tmin)) {
      // NOTE: For the demo, we know what to cast the vertex/index data to.  If
      // we were mixing formats, some metadata would be needed to figure out
      // what to cast it to.
      auto vertices = (Vertex*)geo->VertexBufferCPU->GetBufferPointer();
      auto indices = (std::uint32_t*)geo->IndexBufferCPU->GetBufferPointer();
      UINT triCount = ri->IndexCount / 3;

      // Find the nearest ray/triangle intersection.
      tmin = MathHelper::Infinity;
      for (UINT i = 0; i < triCount; ++i) {
        // Indices for this triangle.
        UINT i0 = indices[i * 3 + 0];
        UINT i1 = indices[i * 3 + 1];
        UINT i2 = indices[i * 3 + 2];

        // Vertices for this triangle.
        XMVECTOR v0 = XMLoadFloat3(&vertices[i0].Pos);
        XMVECTOR v1 = XMLoadFloat3(&vertices[i1].Pos);
        XMVECTOR v2 = XMLoadFloat3(&vertices[i2].Pos);

        // We have to iterate over all the triangles in order to find the
        // nearest intersection.
        float t = 0.0f;
        if (TriangleTests::Intersects(rayOrigin, rayDir, v0, v1, v2, t)) {
          if (t < tmin) {
            // This is the new nearest picked triangle.
            tmin = t;
            UINT pickedTriangle = i;

            mPickedRitem->Visible = true;
            mPickedRitem->IndexCount = 3;
            mPickedRitem->BaseVertexLocation = 0;

            // Picked render item needs same world matrix as object picked.
            mPickedRitem->World = ri->World;
            mPickedRitem->NumFramesDirty = gNumFrameResources;

            // Offset to the picked triangle in the mesh index buffer.
            mPickedRitem->StartIndexLocation = 3 * pickedTriangle;
          }
        }
      }
    }
  }
}

void DxGraphics::Pick_OnMouseDown(WPARAM state, int x, int y) {
  if ((state & MK_RBUTTON) != 0) {
    Pick(x, y);
  }
}

} // namespace ifire